<html>
<head>
<link href="../tutorial.css" rel="stylesheet" type="text/css">
</head>
<body>

<div class="header">
The NakedMud Tutorial :: Storage Sets
</div>

<!-- content starts here -->
<div class="content-wrap"><div class="content-body-wrap"><div class="content">
<div class="head">Storage Sets</div>
<div class="info">
Storage sets are a big part of NakedMud. They serve a few important purposes: 
They simplify the process of saving data from files by eliminating your need to
come up with formatting schemes for your flatfiles. They also eliminate your 
need to write file parsers to extract data from files; the process of retrieving
information from a file is reduced to querying for the value of some key. As we
will learn later, they also play an integral role in the process of saving and 
loading auxiliary data.
<p></p>
In this section, we will learn the ropes of storage sets. We'll see how to 
store and read lists and strings. The other data types storage sets can deal 
with (ints, bools, doubles, longs) are handled in the exact same way as strings,
except with different function names. After this tutorial, you should be able 
to extrapolate how these other data types interact with storage sets. In the 
next section on auxiliary data, we will examine how storage sets work in 
conjunction with auxiliary data.
</div>

<div class="head">The Mail Module</div>
<div class="info">
In the previous section, we designed a 'proof of concept' for a mail system.
This section will build on top of it. If you did not complete the previous
tutorial on the mail module, you can download the source code here.
</div>

<div class="head">Storage Set Basics</div>
<div class="info">
The first thing that is needed are the headers for interacting with storage
sets. Along with all of the other includes, add:

<pre class="code">
#include "../handler.h"       // for giving mail to characters
#include "../storage.h"       // for saving/loading mail
</pre>

Next, define a file where mail will be stored when the mud is down. The MUD's
lib directory seems like an ideal candidate directory. Define the location for
storing mail by where the table for storing mail is located:

<pre class="code">
// this is the file we will save all unreceived mail in, when the mud is down
#define MAIL_FILE "../lib/misc/mail"

// maps charName to a list of mail they have received
HASHTABLE *mail_table = NULL;
</pre>

Two things needed are functions for converting both ways between MAIL_DATA
and STORAGE_SETS. By convention, these functions are called xxxStore and 
xxxRead, where xxx is what is being converted to and from a STORAGE_SET. Get
those functions set up, just below the <i>newMail</i> and <i>deleteMail</i>
functions:

<pre class="code">
// parse a piece of mail from a storage set
MAIL_DATA *mailRead(STORAGE_SET *set) {
  // allocate some memory for the mail
  MAIL_DATA *mail = malloc(sizeof(MAIL_DATA));
  mail->mssg      = newBuffer(1);

  // read in all of our values
  mail->sender = strdup(read_string(set, "sender"));
  mail->time   = strdup(read_string(set, "time"));
  bufferCat(mail->mssg, read_string(set, "mssg"));
  return mail;
}

// represent a piece of mail as a storage set
STORAGE_SET *mailStore(MAIL_DATA *mail) {
  // create a new storage set
  STORAGE_SET *set = new_storage_set();
  store_string(set, "sender", mail->sender);
  store_string(set, "time",   mail->time);
  store_string(set, "mssg",   bufferString(mail->mssg));
  return set;
}
</pre>

Now that there is actually the ability to store and read mail, create two 
functions for actually doing the storing and reading:

<pre class="code">
// saves all of our unreceived mail to disk
void save_mail(void) {
  // make a storage set to hold all our mail
  STORAGE_SET *set = new_storage_set();

  // make a list of name:mail pairs, and store it in the set
  STORAGE_SET_LIST *list = new_storage_list();

  // iterate across all of the people who have not received mail, and
  // store their names in the storage list, along with their mail
  HASH_ITERATOR *mail_i = newHashIterator(mail_table);
  const char      *name = NULL;
  LIST            *mail = NULL;
  ITERATE_HASH(name, mail, mail_i) {
    // create a new storage set that holds each name:mail pair,
    // and add it to our list of all name:mail pairs
    STORAGE_SET *one_pair = new_storage_set();
    store_string    (one_pair, "name", name);
    store_list      (one_pair, "mail", gen_store_list(mail, mailStore));
    storage_list_put(list, one_pair);
  } deleteHashIterator(mail_i);

  // make sure we add the list of name:mail pairs we want to save
  store_list(set, "list", list);

  // now, store our set in the mail file, and clean up our mess
  storage_write(set, MAIL_FILE);
  storage_close(set);
}

// loads all of our unreceived mail from disk
void load_mail(void) {
  // parse our storage set
  STORAGE_SET *set = storage_read(MAIL_FILE);

  // make sure the file existed and wasn't empty
  if(set == NULL) return;

  // get the list of all name:mail pairs, and parse each one
  STORAGE_SET_LIST *list = read_list(set, "list");
  STORAGE_SET  *one_pair = NULL;
  while( (one_pair = storage_list_next(list)) != NULL) {
    const char *name = read_string(one_pair, "name");
    LIST       *mail = gen_read_list(read_list(one_pair, "mail"), mailRead);
    hashPut(mail_table, name, mail);
  }

  // Everything is parsed! Now it's time to clean up our mess
  storage_close(set);
}
</pre>

This code may be a bit ugly to the untrained eye. However, there are some very 
useful nuggets of knowledge buried within it. If you are having troubles 
understanding what is going on, it is highly suggested that you take a few 
minutes to trace through these two functions and figure out what is going on. 
Once we are completely done this section, it may also help to write a couple 
mails to yourself and examine what the mail file looks like. The file's 
structure might help elucidate many of the things that are going on in these 
two functions.

Now that our save and load functions are written, we have to make sure they are
called appropriately. We will want to load up unread mail when the mail module 
initialized, and we will want to make sure we update the contents of the mail 
file whenever mail is sent or received. At the end of cmd_mail, make sure mail
is saved:

<pre class="code">
    // let the character know we've sent the mail
    send_to_char(ch, "You send a message to %s.\r\n", arg);
    
    // save all unread mail
    save_mail();    
</pre>

At the end of cmd_receive, make sure mail is saved:

<pre class="code">
    // let the character know how much mail he received
    send_to_char(ch, "You receive %d letter%s.\r\n", 
                 listSize(mail_list), (listSize(mail_list) == 1 ? "" : "s"));
    
    // update the unread mail in our mail file
    save_mail();
</pre>

Finally, ensure we load up all unread mail when we initialize the module:

<pre class="code">
// boot up the mail module
void init_mail(void) {
  // initialize our mail table
  mail_table = newHashtable();

  // parse any unread mail
  load_mail();
</pre>

Unreceived mail will now be persistent across reboots and crashes. You may 
have noticed that saving of mail is rather inefficient; every time someone
receives a mail or sends a mail, we have to re-save all unreceived mails.
Ideally, we would like to change it so we have to re-save as little information
as possible. That is the problem we will tackle in the section on auxiliary
data.
</div>

<!-- content ends here-->
</div></div></div>

<!-- navigation starts here -->
<div class="nav-wrap"><div class="nav">
<iframe src="nav.html" height="100%" width="100%" scrolling=no frameborder=0>
</iframe>
<!-- navigation ends here -->
</div></div>

<!--div class="footer">Edit Date: Nov 15, 2008. By Geoff Hollis</div-->
</body>
</html>
